#include "video.hpp"
#include "camera.hpp"

#include "SDL.h"
#ifdef GLES
#include <EGL/egl.h>
#include <GLES/gl.h>
#define glOrtho glOrthof
#else
#include "SDL_opengl.h"
#endif

#include <iostream>
#include <stdio.h>
#include <assert.h>

const float Video::WIDTH = 40;
const float Video::HEIGHT = 30;
const float Video::PIX_PER_UNIT = 8;
const float Video::UNIT_PER_PIX = .125f;
static const int BPP = 32;

bool Video::Open(int winwidth, int winheight, Uint32 flags)
{
	// reset
	Look(0, 0);
	look.size = Vec2f(WIDTH, HEIGHT);
	Pos(0, 0);
	Color(1, 1, 1, 1);
	Flip(false, false);
	Scale(1, 1);

	// window 
	if (!WindowOpen(winwidth, winheight, flags))
		return false;

	// view
	Resize(WIDTH, HEIGHT);

	// features
	glClearColor(0, 0, 0, 1);
	glDisable(GL_LIGHTING);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	//glEnable(GL_DEPTH_TEST);
	//glDepthFunc(GL_GEQUAL);
	//glClearDepthf(-1);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GEQUAL, .5f);
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	return true;
}

#ifdef RASPBERRYPI
#include "bcm_host.h"
#endif

bool Video::WindowOpen(int winwidth, int winheight, Uint32 flags)
{
#ifdef GLES
	int32_t success = 0;
	EGLBoolean result;
	EGLint num_config;

	static const EGLint attribs[] =
	{
		EGL_RED_SIZE, 8,
		EGL_GREEN_SIZE, 8,
		EGL_BLUE_SIZE, 8,
		EGL_ALPHA_SIZE, 8,
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
		EGL_NONE
	};

	EGLConfig cfg;

	// get an EGL display connection
	windisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	assert(windisp!=EGL_NO_DISPLAY);

	// initialize the EGL display connection
	result = eglInitialize(windisp, NULL, NULL);
	assert(EGL_FALSE != result);

	// get an appropriate EGL frame buffer cfguration
	result = eglChooseConfig(windisp, attribs, &cfg, 1, &num_config);
	assert(EGL_FALSE != result);

	// create an EGL rendering context
	EGLContext context = eglCreateContext(windisp, cfg, EGL_NO_CONTEXT, NULL);
	assert(context!=EGL_NO_CONTEXT);

#ifdef RASPBERRYPI
	bcm_host_init();

	DISPMANX_ELEMENT_HANDLE_T dispman_element;
	DISPMANX_DISPLAY_HANDLE_T dispman_display;
	DISPMANX_UPDATE_HANDLE_T dispman_update;
	VC_RECT_T dst_rect;
	VC_RECT_T src_rect;

	// create an EGL window surface
	uint32_t w, h;
	success = graphics_get_display_size(0/*LCD*/, &w, &h);
	assert( success >= 0 );
	
	dst_rect.x = 0;
	dst_rect.y = 0;
	dst_rect.width = w;
	dst_rect.height = h;

	src_rect.x = 0;
	src_rect.y = 0;
	src_rect.width = w << 16;
	src_rect.height = h << 16;        

	dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
	dispman_update = vc_dispmanx_update_start( 0 );

	dispman_element = vc_dispmanx_element_add(dispman_update,
			dispman_display,
			0/*layer*/, &dst_rect, 0/*src*/,
			&src_rect, DISPMANX_PROTECTION_NONE,
			0 /*alpha*/, 0/*clamp*/, 
			(DISPMANX_TRANSFORM_T)0/*transform*/);

	static EGL_DISPMANX_WINDOW_T nativewindow;

	nativewindow.element = dispman_element;
	nativewindow.width = w;
	nativewindow.height = h;
	vc_dispmanx_update_submit_sync( dispman_update );

	winwidth = (int)w;
	winheight = (int)h;
#endif

	winsurf = eglCreateWindowSurface(windisp, cfg, &nativewindow, NULL);
	assert(winsurf != EGL_NO_SURFACE);

	// connect the context to the surface
	result = eglMakeCurrent(windisp, winsurf, winsurf, context);
	assert(EGL_FALSE != result);

#else	// PC window
	flags |= SDL_OPENGL; 
	if (!(flags & SDL_FULLSCREEN))
		flags |= SDL_RESIZABLE;
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	//SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
	SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 0);
#endif

	if(SDL_SetVideoMode(winwidth, winheight, BPP, flags) == NULL)
	{
		std::cerr << "Video::Open: " << SDL_GetError() << std::endl;
		return false;
	}

	if (!winwidth || !winheight)
	{
		winwidth = SDL_GetVideoSurface()->w;
		winheight = SDL_GetVideoSurface()->h;
	}

	WinResize(winwidth, winheight, KEEPASPECT);

	return true;
}

void Video::Close()
{
}

enum
{
	VP_X,
	VP_Y,
	VP_W,
	VP_H,
	VP_PARAMCT
};

void Video::Zoom(float zoom)
{
	int vport[VP_PARAMCT];
	glGetIntegerv(GL_VIEWPORT, vport);

	Vec2f winsize (vport[VP_W], vport[VP_H]);
	winsize *= UNIT_PER_PIX / zoom;
	Resize(winsize.x, winsize.y);
}

void Video::Resize(float w, float h)
{
	look.size = Vec2f(w, h);
	Cam.Size(w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, w, h, 0, 1, -1);
}

void Video::ResizeToWin()
{
	int vport[VP_PARAMCT];
	glGetIntegerv(GL_VIEWPORT, vport);

	Vec2f winsize (vport[VP_W], vport[VP_H]);
	winsize *= UNIT_PER_PIX;
	Resize(winsize.x, winsize.y);
}

void Video::WinResize(int winwidth, int winheight, bool keepaspect)
{
#if !defined WIN32
	Uint32 flags = SDL_GetVideoSurface()->flags;
	if (!SDL_SetVideoMode(winwidth, winheight, BPP, flags))
		return;	
#endif
	Box2i vport (Vec2i(0, 0), Vec2i(winwidth, winheight));

	if (keepaspect)
	{
		if (winheight > look.InvRatio() * winwidth)
		{
			vport.size.y = look.InvRatio() * winwidth;
			vport.off.y = (winheight - vport.size.y) / 2;
		}
		else
		{
			vport.size.x = look.Ratio() * winheight;
			vport.off.x = (winwidth - vport.size.x) / 2;
		}
	}

	glViewport(vport.off.x, vport.off.y, vport.size.x, vport.size.y);
}

void Video::Look(float left, float top)
{
	look.off.x = left;
	look.off.y = top;
}
	
void Video::Clear()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Video::Pos(float left, float top)
{
	draw.x = left - look.off.x;
	draw.y = top - look.off.y;
}

void Video::Pri(float pri)
{
	z = pri;
}

void Video::Color(float red, float green, float blue, float alpha)
{
	r = red;
	g = green;
	b = blue;
	a = alpha;
}

void Video::Flip(bool hori, bool vert)
{
	flip.x = hori;
	flip.y = vert;
}

void Video::Scale(float sx, float sy)
{
	scale.x = sx;
	scale.y = sy;
}

void Video::BoxDraw(float width, float height, bool fill)
{
	glDisable(GL_TEXTURE_2D);
	glColor4f(r, g, b, a);

	float vt[] =
	{
		draw.x + 0, draw.y + 0,
		draw.x + 0, draw.y + height,
		draw.x + width, draw.y + height,
		draw.x + width, draw.y + 0,
	};

	glVertexPointer(2, GL_FLOAT, 0, vt);
	glDrawArrays(GL_LINE_LOOP, 0, 4);
}

#include "image.hpp"

void Video::TilesDraw(Image & img, const wchar_t *tiles,
		      int tcols, int trows)
{
	if(draw.x >= look.size.x || draw.y >= look.size.y)
		return; 

	float totalw = img.TileWidth() * tcols * scale.x;
	float totalh = img.TileHeight() * trows * scale.y;

	if (draw.x + totalw < 0 || draw.y + totalh < 0)
		return;

	// Yes, only correct for tile size (1, 1) and no view rotation.
	// But in this game, smaller tile sizes and view rotation aren't used,
	// and grids with larger tile sizes shouldn't have many tiles
	int c1 = -draw.x / (scale.x * img.TileWidth());
	int r1 = -draw.y / (scale.y * img.TileHeight());
	int cols = look.size.x / scale.x + 1;
	int r2 = look.size.y / scale.y + r1;
	c1 = std::max<int>(0, c1);
	cols = std::min<int>(cols, tcols - c1);
	r1 = std::max<int>(0, r1);
	r2 = std::min<int>(r2, trows - 1);

	Matf modelview;
	modelview.Translate(c1, r1, 0);
	modelview.Scale(scale.x, scale.y, 1);
	modelview.Translate(draw.x, draw.y, 0);
	//Vecf rdraw (draw.x + (c1 * scale.x * img.TileWidth()),
	//		draw.y + (r1 * scale.y * img.TileHeight()));

	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();
	//glTranslatef(rdrawx, rdrawy, z);
	//glScalef(scale.x, scale.y, 1);	
	glEnable(GL_TEXTURE_2D);
	glColor4f(r, g, b, a);

	for (int r = r1; r <= r2; ++r)
	{
		img.RowDraw(&tiles[(r * tcols) + c1], cols, modelview);
		modelview.Translate(0, scale.y * img.TileHeight(), 0);
		//glTranslatef(0, img.TileHeight(), 0);
	}
	//img.GridDraw(tiles, tcols, trows);
}

void Video::SpriteDraw(Image & img, int tile)
{
	if(draw.x > look.size.x || draw.y > look.size.y)
		return; 

	float totalw = img.TileWidth() * scale.x;
	float totalh = img.TileHeight() * scale.y;

	if (draw.x + totalw < 0 || draw.y + totalh < 0)
		return;

	Vec2f sprdraw (draw);
	//if (flip.x)
	//	sprdraw.x += totalw;
	//if (flip.y)
	//	sprdraw.y += totalh;
	Vec2f sprscale (scale);
	if (flip.x)
		sprscale.x = -sprscale.x;
	if (flip.y)
		sprscale.y = -sprscale.y;

	Matf modelview;
	modelview.Scale(sprscale.x, sprscale.y, 1);
	modelview.Translate(sprdraw.x, sprdraw.y, 0);

//	glMatrixMode(GL_MODELVIEW);
//	glLoadIdentity();
//	glTranslatef(realx, realy, z);
//	glScalef(flip.x ? -scale.x : scale.x, flip.y ? -scale.y : scale.y, 1);	
	glEnable(GL_TEXTURE_2D);
	glColor4f(r, g, b, a);

	img.Draw(tile, modelview);
}

void Video::SpriteBoxDraw(Image & img)
{
	if(draw.x > look.size.x || draw.y > look.size.y)
		return; 

	float totalw = img.TileWidth() * scale.x;
	float totalh = img.TileHeight() * scale.y;

	if (draw.x + totalw < 0 || draw.y + totalh < 0)
		return;

	Vec2f sprdraw (draw);
	//if (flip.x)
	//	sprdraw.x += totalw;
	//if (flip.y)
	//	sprdraw.y += totalh;
	Vec2f sprscale (scale);
	if (flip.x)
		sprscale.x = -sprscale.x;
	if (flip.y)
		sprscale.y = -sprscale.y;

	Matf modelview;
	modelview.Scale(sprscale.x, sprscale.y, 1);
	modelview.Translate(sprdraw.x, sprdraw.y, 0);

	glDisable(GL_TEXTURE_2D);
	glColor4f(r, g, b, a);
	img.BoxDraw(modelview);
}

void Video::DrawEnd()
{
#ifdef GLES
	eglSwapBuffers(windisp, winsurf);
#else
	SDL_GL_SwapBuffers();
#endif
}

Video Vid;
